﻿#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using TrackerRealm.Jazz.Common;

namespace TrackerRealm.Jazz.Client
{

    /// <summary>
    /// Contains a cache of objects that have been bound (see Nexus.Bind method) 
    /// to the client nexus.
    /// <para>The cache is indexed by ID (Guid), jWorkflow type and 'Name'.</para>
    /// <remarks><para>--- does not contain all storage units in the Server Nexus but
    /// will contain all units that are retrieved for the server nexus 
    /// at intialiazation </para>and
    /// units that have been created and stored (marked confirmed)
    /// since the client nexus constructor executed.---</remarks>
    /// </summary>
    public class Cache: IEnumerable<IjStorageUnit>
    {
        /// <summary>
        /// Used to provide storage.
        /// </summary>
        protected Dictionary<Guid, IjStorageUnit> storageDict
            = new Dictionary<Guid, IjStorageUnit>();
        #region CacheByName
        /// <summary>
        /// 
        /// </summary>
        protected CacheByName<IjStorageUnit> namedObjects; 
        /// <summary>
        /// Reurns a collection that is stored by 'Name'.
        /// </summary>
        public CacheByName<IjStorageUnit> NamedObjects
        {
            get { return this.namedObjects; }
        }
        #endregion
        private ClientNexusBase nexus;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="nexus"></param>
        public Cache(ClientNexusBase nexus)
        {
            this.nexus = nexus;
            this.namedObjects = new CacheByName<IjStorageUnit>(nexus);
        }
        ///// <summary>
        ///// 
        ///// </summary>
        //internal void NameObjectsRefresh()
        //{
        //    this.namedObjects.Clear();
        //    //foreach (KeyValuePair<string, IjStorageUnit> kvp in this.storageDict)
        //    foreach (KeyValuePair<Guid, IjStorageUnit> kvp in this.storageDict)
        //    {
        //        if (kvp.Value.StorageType == jStorageType.JazzObject &&
        //            kvp.Value.Name != "")
        //            this.namedObjects.Add(kvp.Value);
        //    }
        //}
        #region JClasses
        private CacheByType<jClass> cacheByType = new CacheByType<jClass>();
        /// <summary>
        /// 
        /// </summary>
        public CacheByType<jClass> JClasses
        {
            get { return this.cacheByType; }
        }
        #endregion
        /// <summary>
        /// 
        /// </summary>
        /// <param name="unit"></param>
        /// <returns></returns>
        protected bool TryRemoveNamedObjectUnit_(IjStorageUnit unit)
        {
            return this.namedObjects.TryRemove(unit);
        }
        #region Count
        /// <summary>
        /// Returns a count of all '<see cref="IjStorageUnit"/>'s cached.
        /// </summary>
        public int Count
        {
            get { return this.storageDict.Count; }
        }
        #endregion
        #region IsStorageConsistent
        /// <summary>
        /// Checks the storage to insure the keys are consistent with units in storage.
        /// If false is returned it most likely indicates that there is a problem with the
        /// deserialization software (TjXmlDom).
        /// </summary>
        /// <returns></returns>
        public bool IsStorageConsistent()
        {
            //foreach (KeyValuePair<string, IjStorageUnit> kvp in this.storageDict)
            foreach (KeyValuePair<Guid, IjStorageUnit> kvp in this.storageDict)
            {
                if (kvp.Key != kvp.Value.ID)
                    return false;
            }
            return true;
        }
        #endregion
        #region IEnumerable<IjStorageUnit> Members
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerator<IjStorageUnit> GetEnumerator()
        {
            return this.storageDict.Values.GetEnumerator();
        }
        #endregion

        #region IEnumerable Members
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.storageDict.Values.GetEnumerator();
        }
        #endregion
        #region Contains
        /// <summary>
        /// 
        /// </summary>
        /// <param name="guid"></param>
        /// <returns></returns>
        public bool Contains(Guid guid)
        {
            return this.storageDict.ContainsKey(guid);
        }
        #endregion
        #region TryGetStorageUnit
        /// <summary>
        /// 
        /// </summary>
        /// <param name="guid"></param>
        /// <param name="unit"></param>
        /// <returns></returns>
        public bool TryGetStorageUnit(Guid guid, out IjStorageUnit unit)
        {
            return this.storageDict.TryGetValue(guid, out unit);
        }
        #endregion
        #region TryGetStorageUnit
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="guid"></param>
        /// <param name="unit"></param>
        /// <returns></returns>
        public bool TryGetStorageUnit<T>(Guid guid, out T unit) where T : class
        {
            IjStorageUnit u;
            if (this.storageDict.TryGetValue(guid, out u))
            {
                unit = u as T;
                if (unit != null) return true;
            }
            unit = null;
            return false;
        }
        #endregion
        #region GetUnit
        /// <summary>
        /// Returns the object from the nexus cache for the 
        /// given '<paramref name="guid"/>'.
        /// An exception will be raised if there is not object for the 
        /// given '<paramref name="guid"/>' or
        /// if the object is not of type T.
        /// </summary>
        /// <param name="guid"></param>
        /// <returns></returns>
        public T GetUnit<T>(Guid guid) where T : class
        {
            IjStorageUnit unit;
            if (!this.TryGetStorageUnit(guid, out unit))
                throw new Exception(string.Format(
                    "The object with the guid '{0}' was not found.",
                    guid));
            T returnObj;
            if (typeof(T) == typeof(jObject))
            {
                object o = (jObject)(unit as jObject);
                returnObj = (T)o;
            }
            else returnObj = unit as T;
            if (returnObj == null)
                throw new Exception(string.Format(
                    "The object with the guid '{0}' was found but it did not a have a type '{1}'.  " +
                    "The found unit with the name '{2}' was of type '{3}'.",
                    guid, typeof(T).Name, unit.Name, unit.GetType().FullName));
            return returnObj;
        }
        #endregion
        #region AddStorageUnit
        /// <summary>
        /// Adds an 'unit' to the Cache.
        /// This method is automatically called when objects are bound to Client Nexus (see the method ClientNexus.Bind()).
        /// </summary>
        /// <param name="unit"></param>
        internal void Add(IjStorageUnit unit)
        {
            if (this.storageDict.ContainsKey(unit.ID))
            {
                // replace the stored unit if it is a link object.
                IjStorageUnit unitStored = this.storageDict[unit.ID];
                if (unitStored.StorageType == jStorageType.LinkObject)
                    this.storageDict[unit.ID] = unit;
                return;
            }
            // add to dictionary
            this.storageDict.Add(unit.ID, unit);
            // add to name dictionary
            if (unit.Name != null && unit.Name != "" && unit.StorageType == jStorageType.JazzObject)
                this.namedObjects.Add(unit);
            // add to jClass dictionary
            if (unit.StorageType == jStorageType.Class)
                this.JClasses.Add((jClass)unit);
        }
        #endregion
        #region Remove
        internal void Remove(Guid id)
        {
            this.storageDict.Remove(id);
        }
        /// <summary>
        /// <para>Internal method.</para>
        /// <para>
        /// Removes the '<see cref="IjStorageUnit"/>s' specified by
        /// the '<paramref name="guids"/>' from storage.   Does
        /// not remove the '<see cref="IjStorageUnit"/>s' from the server nexus.</para>
        /// </summary>
        /// <param name="guids"></param>
        internal void Remove(Guid[] guids)
        {
            foreach (Guid guid in guids)
            {
                IjStorageUnit unit;
                if (this.storageDict.TryGetValue(guid, out unit))
                {
                    this.TryRemoveNamedObjectUnit_(unit);
                }
                this.storageDict.Remove(guid);

            }
        }
        
        #endregion
        
    }

}

